<?php
/**
 * Work as
 * + Regitry
 * + Autoload
 * + Error handler
 * + Exception handler
 * @final
 * @author    Nam Nguyen <nam.ngvan@gmail.com>
 * @since     Apr 11, 2013
 * @version   1.0.0
 */
final class Nuo
{
	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	CONST INFO = 'INFO';

	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	CONST DEBUG = 'DEBUG';

	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	CONST ERROR = 'ERROR';

	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	CONST WARNING = 'WARNING';

	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	CONST NOTICE = 'NOTICE';

	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	CONST FATAL = 'FATAL';

	/**
	 * Constant
	 * @access  public
	 * @var     string
	 */
	const VERSION = '1.0';

	/**
	 * Store register data
	 * @static
	 * @access   private
	 * @var      array
	 */
	static private $_vars = array();

	/**
	 * Nuo_User_Interface
	 * @static
	 * @access   public
	 * @var      Nuo_User_Interface
	 */
	static private $_user = NULL;

	/**
	 * @static
	 * @access private
	 * @var array
	 */
	static private $_modules = NULL;

	/**
	 * @static
	 * @access private
	 * @var object registry
	 */
	static private $_objs = array();

	/**
	 * request code
	 * @static
	 * @access private
	 * @var string
	 */
	static private $_code = NULL;

	/**
	 * @static
	 * @access private
	 * @var array
	 */
	static private $_resources = array();

	/**
	 * @static
	 * @access private
	 * @var Nuo_Request_Abstract
	 */
	static private $_request = null;

	/**
	 * @static
	 * @access private
	 * @var Nuo_Response
	 */
	static private $_response = null;

	/**
	 * @static
	 * @access private
	 * @var Nuo_Document
	 */
	static private $_document = null;

	/**
	 * @static
	 * @access private
	 * @var Nuo_Router
	 */
	static private $_router = null;

	/**
	 * @static
	 * @access private
	 * @var Nuo_Router
	 */
	static private $_view = null;

	/**
	 * @static
	 * @access private
	 * @var Nuo_Hook
	 */
	static private $_hook = null;

	/**
	 * Get value assigned to KEY.
	 * @static
	 * @access  public
	 * @param   string   $key
	 * @return  any
	 */
	static public function getVar($key)
	{
		return isset(self::$_vars[$key]) ? self::$_vars[$key] : NULL;
	}

	/**
	 * Set data
	 * @static
	 * @access  public
	 * @param   string  $key
	 * @param   any     $value
	 * @return  void
	 */
	static public function setVar($key, $val)
	{
		self::$_vars[(string)$key] = $val;
	}

	/**
	 * Clear all data
	 * @static
	 * @access   public
	 * @return   void
	 */
	static public function clearVar()
	{
		self::$_vars = array();
	}

	/**
	 * $key exists
	 * @access   public
	 * @param    string   $key
	 * @return   bool
	 */
	static function hasVar($key)
	{
		return isset(self::$_vars[(string)$key]);
	}

	/**
	 * Get code Id of current request
	 * @static
	 * @access public
	 * @return string
	 */
	static function getCode()
	{
		if (NULL == self::$_code)
		{
			self::$_code = get_random_text(8);
		}
		return self::$_code;
	}

	/**
	 * This method should be called once
	 * @static
	 * @access public
	 * @return void
	 */
	static public function init()
	{
		/**
		 * @var bool
		 */
		static $init = FALSE;

		if ($init)
		{
			return;
		}

		$init = TRUE;

		// register autoload register
		spl_autoload_register(array(
			'Nuo',
			'loadClass'
		));

		/**
		 * set exception handler
		 */
		set_exception_handler(array(
			'Nuo',
			'handleException'
		));

		/**
		 * set error handler
		 */
		set_error_handler(array(
			'Nuo',
			'handleError'
		));

		/**
		 * register handle shutdown function to process fatal error without show error
		 * message to screen.
		 */
		register_shutdown_function(array(
			'Nuo',
			'handleShutdown'
		));

		Intl::instance();

		/**
		 * Init session
		 */
		Nuo_Session::init();

		/**
		 * Init module
		 */
		Nuo::_initModules();

		/**
		 * Init resource
		 */
		Nuo::_initResources();
	}

	/**
	 * Read a config file under ./application/setting/
	 *
	 * @static
	 * @access public
	 * @param  string  $file
	 * @return  array | NULL
	 */
	static function readConfigFile($name)
	{
		if (file_exists($file = SETTING_DIR . $name . '.php'))
		{
			return
			include $file;
		}
	}

	/**
	 * get global config
	 * @static
	 * @access public
	 * @param   string  $name
	 * @param   string  $key
	 * @return  string/int/array/object
	 */
	static public function getConfig($group, $key = NULL)
	{
		return Nuo::getApi('core/config') -> read($group, $key);
	}

	/**
	 * This method is called by spl_autoload
	 * @static
	 * @access  public
	 * @param   string  $class  class name
	 * @return  bool
	 */
	static function loadClass($class)
	{
		$class = strtolower($class);

		$arr = array(
			ROOT_DIR . '/application/library/',
			ROOT_DIR . '/application/module/',
		);

		list($k1, $k2) = explode('_', $class, 2);

		if (!$k2)
		{
			return
			require_once $k1 . '.class.php';
		}

		$sub = str_replace('_', '/', $k2) . '.class.php';

		foreach ($arr as $path)
		{
			/**
			 * Load classes under ".module/" directory
			 */
			$file = $path . $k1 . '/classes/' . $sub;

			if (file_exists($file))
			{
				require_once $file;
				return TRUE;
			}
		}
		return FALSE;
	}

	/**
	 * Load module class
	 * @static
	 * @access  public
	 * @param   string $class
	 * @return  bool
	 */
	static public function loadModuleClass($class)
	{
		if (class_exists($class, FALSE))
		{
			return TRUE;
		}

		$class = strtolower($class);

		/**
		 * Load classes under ".module/" directory
		 */
		list($k1, $k2) = explode('_', $class, 2);

		$file = MODULE_DIR . $k1 . '/classes/' . str_replace('_', '/', $k2) . '.class.php';

		if (file_exists($file))
		{
			require_once $file;
			return TRUE;
		}

		return FALSE;
	}

	/**
	 * Handle shut down function
	 * This method is called by php shutdown handler
	 * @link  http://php.net/manual/en/function.register-shutdown-function.php
	 * @static
	 * @access public
	 * @return void
	 */
	static function handleShutdown()
	{
		// start profiler if needed

		$profile = array(
			'total' => Nuo_Profiler::endRequest(),
			'stats' => Nuo_Profiler::stats(),
			'detail' => Nuo_Profiler::detail(),
		);
		// Nuo_Log::instance('profiler') -> write($profile);
		/*
		 */

		if (function_exists('error_get_last'))
		{
			if ($err = error_get_last())
			{
				self::handleFatalError($err);
			}
		}
	}

	/**
	 * @link   http://php.net/manual/en/function.set-exception-handler.php
	 * @static
	 * @access public
	 * @param  Exception $exception
	 * @return bool
	 */
	static function handleException($e)
	{
		$msg = implode(PHP_EOL, array(
			'message: ' . $e -> getMessage() . ' (' . $e -> getCode() . ')',
			'file: ' . $e -> getFile() . ' (' . $e -> getLine() . ')',
			Nuo_Debug::getTrace()
		));

		Nuo_Log::instance() -> write($msg, Nuo::FATAL);

		return TRUE;
	}

	/**
	 * @static
	 * @access public
	 * @param  int  $no
	 * @param  string  $msg
	 * @param  string  $file
	 * @param  string  $line
	 * @param  string  $context
	 * @return bool
	 */
	static public function handleError($no, $msg = NULL, $file = NULL, $line = NULL, $context = NULL)
	{
		$level = Nuo::NOTICE;
		$fatal = FALSE;

		if ($no & NUO_ERROR)
		{
			$level = Nuo::ERROR;
			$fatal = TRUE;
		}
		else
		if ($no & NUO_WARNING)
		{
			$level = Nuo::WARNING;
		}

		$msg = implode(PHP_EOL, array(
			'error: ' . $msg . ' (' . $no . ')',
			'file: ' . $file . ' (' . $line . ')',
			Nuo_Debug::getTrace()
		));

		Nuo_Log::instance() -> write($msg, $level);

		$fatal && self::handleFatalError();

		return TRUE;
	}

	/**
	 * Handle faltar error
	 *
	 * @static
	 * @access  public
	 * @param   array $error
	 * @return  void
	 */
	static function handleFatalError($err = NULL)
	{
		// Clean any previous output from buffer
		while (ob_get_level() > 0)
		{
			ob_end_clean();
		}

		if ($err)
		{
			$msg = implode(PHP_EOL, array(
				'error: ' . $err['message'] . ' (' . $err['type'] . ')',
				'file: ' . $err['file'] . ' (' . $err['line'] . ')',
			));

			Nuo_Log::instance() -> write($msg, Nuo::FATAL);

			echo '<pre>' . $msg . '</pre>';
		}

	}

	/**
	 * Init enabled module
	 * @static
	 * @access  private
	 * @return  void
	 */
	static private function _initModules()
	{
		$key = 'core/config/module';

		if (FALSE === (self::$_modules = Nuo_Cache::instance() -> read($key)))
		{

			$db = Database::instance();
			$sql = 'select * from nuo_core_module where is_enabled=1 order by sort_order ASC';
			
			foreach ($db->fetchAll($sql) as $row)
			{
				$data[$row['name']] = $row['name'];
			}
			
			Nuo_Cache::instance() -> write($key, $data);
			
			self::$_modules = $data;
		}
	}

	/**
	 * Init resource stype
	 *
	 * @static
	 * @access  private
	 * @return  void
	 */
	static private function _initResources()
	{
		$key = 'core/config/resource';

		$data = Nuo_Cache::instance() -> read($key);

		if (FALSE == $data)
		{
			$data = array();

			$sql = "SELECT resource_id,name,module,title FROM nuo_core_resource WHERE is_enabled=1 ORDER BY sort_order DESC";

			foreach (Database::instance()->fetchAll($sql) as $row)
			{
				$data[$row['name']] = $row;
			}
			Nuo_Cache::instance() -> write($key, $data);
		}

		self::$_resources = $data;
	}

	/**
	 * Has module  & module is enabled ?
	 *
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @return  bool
	 */
	static public function hasModule($name)
	{
		return isset(self::$_modules[$name]);
	}

	/**
	 * Get all enabled modules
	 *
	 * @static
	 * @access   public
	 *
	 * @return   array
	 */
	static public function getModules()
	{
		return self::$_modules;
	}

	/**
	 * Get Nuo current version
	 * @static
	 * @access  public
	 * @return  string
	 */
	static function getVersion()
	{
		return self::VERSION;
	}

	/**
	 * @static
	 * @access  public
	 * @param   string $name
	 * @param   string $type
	 * @return  object
	 */
	static public function getObject($name, $type = NULL)
	{
		$name = str_replace('-', '/', $name);

		if ($pos = strpos($name, '/'))
		{
			$module = substr($name, 0, $pos);
			$name = substr($name, $pos + 1);
		}
		else
		{
			$module = $name;
		}

		if ($type)
		{
			$class = inflect($module . '/' . $type . '/' . $name);
		}
		else
		{
			$class = inflect($module . '/' . $name);
		}

		if (!class_exists($class, FALSE))
		{
			if ($type)
			{

				$file = MODULE_DIR . $module . '/classes/' . $type . '/' . $name . '.class.php';
			}
			else
			{
				$file = MODULE_DIR . $module . '/classes/' . $name . '.class.php';
			}

			if (file_exists($file))
			{
				require_once $file;
			}
			else
			{
				return NULL;
			}
		}

		return new $class;
	}

	/**
	 * Get Table instance
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @return  Database_Table
	 */
	static public function getTable($name)
	{
		$k = 'table.' . $name;
		return isset(self::$_objs[$k]) ? self::$_objs[$k] : (self::$_objs[$k] = self::getObject($name, 'table'));
	}

	/**
	 * Get Table instance
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @return  Database_Table
	 */
	static public function getPlugin($name)
	{
		$k = 'plugin.' . $name;
		return isset(self::$_objs[$k]) ? self::$_objs[$k] : (self::$_objs[$k] = self::getObject($name, 'plugin'));
	}

	/**
	 * Get Table instance
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @return  Database_Table
	 */
	static public function getApi($name)
	{
		$k = 'api.' . $name;
		return isset(self::$_objs[$k]) ? self::$_objs[$k] : (self::$_objs[$k] = self::getObject($name, 'api'));
	}

	/**
	 * Get full scheme name
	 *
	 * @static
	 * @access  public
	 *
	 * @param   string   $name
	 * @return  string
	 */
	static public function getT($name)
	{
		return 'nuo_' . $name;
	}

	/**
	 * Redirect this current request to new location
	 * <code>
	 * Response::redirect('admin/log',array(id=>1))
	 * </code>
	 * @static
	 * @access  public
	 * @param   string $path
	 * @param   array|string $params optional = null
	 * @param   string $basePath optional = null
	 * @return  void
	 */
	static public function redirect($uri)
	{
		header('location:' . $uri);
		exit(0);
	}

	/**
	 * Compile to full path to application
	 * <code>
	 * Nuo::getUrl()
	 * </code>
	 * @static
	 * @access  public
	 * @param   string/array $path
	 * @param   string/array $param
	 * @param   string $host
	 * @return  string
	 */
	static public function getUrl($path = '', $params = NULL, $host = NULL)
	{
		if ($params)
		{
			$params = '?' . http_build_query($params, NULL, '&');
		}
		else
		{
			$params = '';
		}

		return NUO_BASE_URL . trim($path, '/') . $params;
	}

	/**
	 * Compile to full path to static url resource
	 * <code>
	 * Nuo::getStaticUrl()
	 * </code>
	 * @static
	 * @access  public
	 * @param   string $path
	 * @param   array  $param  optional
	 * @param   string $host   optional
	 * @return  string
	 */
	static public function getStaticUrl($path = '', $params = NULL, $host = NULL)
	{
		static $base;

		if (null == $base)
		{
			$base = Nuo::getConfig('core/site', 'staticUrl');
			if (null == $base)
			{
				$base = Nuo::getUrl('static');
			}

			$base .= '/';
		}

		if ($params)
		{
			$params = '?' . http_build_query($params, NULL, '&');
		}
		else
		{
			$params = '';
		}
		return $base . trim($path, '/') . $params;
	}

	/**
	 * Get resource object
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @return  Database_Table
	 */
	static public function getResource($name)
	{
		if (isset(self::$_resources[$name]))
		{
			return self::$_resources[$name];
		}

		return NULL;
	}

	/**
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @param   array   $spec
	 * @return  void
	 */
	static public function setResource($name, $spec)
	{
		self::$_resources[$name] = $spec;
	}

	/**
	 * Resource exists?
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @return  bool
	 */
	static public function hasResource($name)
	{
		return isset(self::$_resources[$name]);
	}

	/**
	 * get item table
	 * <code>
	 * Nuo::getItem('user',1)
	 * Nuo::getItem('user_1')
	 * </code>
	 * @static
	 * @access  public
	 * @param   string  $name Resource Name or ItemId
	 * @param   int     $id   optional
	 * @return  Object/Null
	 */
	static public function getItem($name, $id = NULL)
	{

		$item = NULL;

		if (!$name)
		{
			return $item;
		}

		if (NULL === $id)
		{
			list($name, $id) = explode('_', $name);
		}

		/**
		 * Check if no $name
		 */
		if (!$name or !$id)
		{
			return $item;
		}

		$k = $name . '.' . $id;

		/**
		 * Table exists
		 */
		if (NULL != ($table = Nuo::getTable($name)))
		{
			$item = $table -> find($id) -> current();
		}

		return $storage[$k] = $item;
	}

	/**
	 * Get Nuo_User_Interface object
	 * @static
	 * @access  public
	 * @return  Nuo_User_Interface
	 */
	static function getUser()
	{
		if (NULL == self::$_user)
		{
			$user = FALSE;

			if (isset($_SESSION['user_id']))
			{
				$id = $_SESSION['user_id'];
			}

			if ($id)
			{
				$user = Nuo::getTable('user') -> find($id) -> current();
			}

			if (!is_object($user))
			{
				// clear session to avoid db check in future
				if (isset($_SESSION['user_id']))
				{
					unset($_SESSION['user_id']);
				}
			}
			/**
			 * set viewer
			 */
			self::setUser($user);
		}

		return self::$_user;
	}

	/**
	 * Get Nuo_User_Interface object
	 * <code>
	 * 	Nuo::setViewer(false); set current viewer is guests
	 * <code>
	 * @static
	 * @access  public
	 * @param   Nuo_User_Interface|false  $viewer
	 * @return  void
	 */
	static function setUser($user)
	{
		if ($user instanceof Nuo_User_Interface)
		{
			self::$_user = $user;
		}
		else
		{
			self::$_user = new Nuo_User_Guest;
		}
	}

	/**
	 * Alias to Nuo::getUser()->isUser()
	 * @static
	 * @access     public
	 * @return     bool
	 */
	static function isMember()
	{
		return self::getUser() -> isMember();
	}

	/**
	 * Alias to Nuo::getUser()->isGuest()
	 * @static
	 * @access     public
	 * @return     bool
	 */
	static function isGuest()
	{
		return self::getUser() -> isGuest();
	}

	/**
	 * Alias to Nuo::getUser()->getId()
	 * @static
	 * @access     public
	 * @return     bool
	 */
	static function getUserId()
	{
		return self::getUser() -> getId();
	}

	/**
	 * Alias to Nuo::getUser()->allow()
	 * @static
	 * @access     public
	 * @param      string  $action
	 * @return     bool
	 */
	static function allow($action)
	{
		return self::getUser() -> allow($action);
	}

	/**
	 * Alias to Nuo::getUser()->required()
	 * @static
	 * @access     public
	 * @param      string  $action
	 * @return     bool
	 */
	static function required($action)
	{
		return self::getUser() -> required($action);
	}

	/**
	 * Alias to Nuo::getUser()->isOwner()
	 * <code>
	 * Nuo::isOwner($item)
	 * result: true/false
	 * </code>
	 * @static
	 * @access     public
	 * @param      Object  $object
	 * @return     bool
	 */
	static function isOwner($object)
	{
		return self::getUser() -> isOwner($object);
	}

	/**
	 * Singleton method to get request object
	 * @static
	 * @access  public
	 * @return  Nuo_Request
	 */
	static function getRequest()
	{
		if (NULL == self::$_request)
		{
			if (php_sapi_name() == 'cli')
			{
				self::$_request = new Nuo_Request_Cmd;
			}
			else
			{
				self::$_request = new Nuo_Request_Http;
			}
		}
		return self::$_request;
	}

	/**
	 * Singleton method to get response object
	 * @static
	 * @access  public
	 * @return  Nuo_Response
	 */
	static function getResponse()
	{
		if (null == self::$_response)
		{
			self::$_response = new Nuo_Response;
		}
		return self::$_response;
	}

	/**
	 * Singleton method to get main router
	 * @static
	 * @access  public
	 * @return  Nuo_Router
	 */
	static function getRouter()
	{
		if (null == self::$_router)
		{
			self::$_router = new Nuo_Router;
		}
		return self::$_router;
	}

	/**
	 * Singleton method of document
	 * @static
	 * @access  public
	 * @return  Nuo_Document
	 */
	static function getDocument()
	{
		if (null == self::$_document)
		{
			self::$_document = new Nuo_Document;
		}
		return self::$_document;
	}

	/**
	 * Singleton method of view
	 * @static
	 * @access  public
	 * @return  Nuo_View
	 */
	static function getView()
	{
		if (null == self::$_view)
		{
			self::$_view = new Nuo_View;
		}
		return self::$_view;
	}

	/**
	 * Singleton method of hook
	 * @static
	 * @access  public
	 * @return  Nuo_View
	 */
	static function getHook()
	{
		if (null == self::$_hook)
		{
			self::$_hook = new Nuo_Hook;
		}
		return self::$_hook;
	}

	/**
	 * Render widget by name
	 * <code>
	 * Nuo::renderWidget('core/widget/browse/minimenu')
	 * result: xhtml string of browse mini menu
	 * </code>
	 * @static
	 * @access  public
	 * @param   string  $name
	 * @param   array   $params optional
	 * @return  string
	 */
	static function renderWidget($name, $params = NULL)
	{

		$name = inflect($name);

		if (Nuo::loadModuleClass($name))
		{
			$widget = new $name($params);
			return $widget -> execute();
		}

	}

}
